#include "TaskManager.h"

#include <string.h>	// declares NULL
#include <stdio.h> // printf

#ifdef _WIN32
#ifdef _DEBUG
   #include <crtdbg.h>
   #undef THIS_FILE
   static char THIS_FILE[] = __FILE__;
   #define new       new( _NORMAL_BLOCK, __FILE__, __LINE__)
   #define malloc(s) _malloc_dbg(s, _NORMAL_BLOCK, __FILE__, __LINE__)
#endif
#endif

// Constructor
TaskManager::TaskManager() {
	// initialize unique_id
	this->id = BN_new();
	if(this->id == NULL) {
		printf("[TaskManager::TaskManager()] BN_new() failed\n");
		exit(1);
	}
	// set unique_id to start with 1
	BN_set_word(this->id, 1);

	// blank out link list of tasks
	this->head = NULL;

	// Is there a state file to load?
	enum Section {
		TASK_MANAGER,
		TASK,
		DETAIL,
		BLOCK_MANAGER,
		BLOCK
	}	section;

	ParamManager pm;
	Block *block = NULL;
	char index[DEFAULT_BUFLEN];

	Task *task = NULL;
	FILE *file = fopen("state.ini", "r");
	if(file != NULL) {
#ifdef _DEBUG
		printf("Loading State ...\n");
#endif // _DEBUG
		char line[DEFAULT_BUFLEN];
		while(fgets(line, DEFAULT_BUFLEN, file)) {
			if(strchr(line, '\r')) {
				char *ptr = strchr(line, '\r');
				ptr[0] = '\0';
			}
			else {
				char *ptr = strchr(line, '\n');
				ptr[0] = '\0';
			}

			if(!strcmp(line, "[task_manager]")) {
				section = TASK_MANAGER;			
			}
			else if(!strcmp(line, "[task]")) {
				section = TASK;

				// loading up another task
				task = this->add();

				// the previous line incremented the unique_id, so we need to back up one
				BN_sub_word(this->id, 1);
			}
			else if(!strcmp(line, "[detail]")) {
				section = DETAIL;
			}
			else if(!strcmp(line, "[block_manager]")) {
				section = BLOCK_MANAGER;
			}
			else if(!strcmp(line, "[block]")) {
				section = BLOCK;

				// reset unique id back to zero before getting a new block
				BN_set_word(task->bm.index, 0);
				block = task->bm.get();
			}
			else {
				pm.init(line, "|");
				switch(section) {
					case TASK_MANAGER:
						if(!strcmp(pm.head->data, "id")) {
							// restore unique_id
							BN_dec2bn(&(this->id), pm.get("id"));
						}
						break;
					case TASK:
						if(!strcmp(pm.head->data, "status")) {
							task->state = (State) atoi(pm.get("status"));
							if((task->state == ACTIVE) || (task->state == COMPLETE)) {
								task->sw.start("main");
							}
						}
						else if(!strcmp(pm.head->data, "id")) {
							BIGNUM *tmp = BN_new();
							BN_init(tmp);
							BN_dec2bn(&(tmp), pm.get("id"));
							OPENSSL_free(task->id);
							task->id = BN_bn2dec(tmp);	// needs to be freed with OPENSSL_free
														// but it is going to be freed with delete[]
							BN_free(tmp);
						}
						break;
					case DETAIL:
						task->sm.set(pm.head->data, pm.head->value);

						// Is this a timestamp?  If so, we are loading a task previously started.
						if(!strcmp(pm.head->data, "timestamp")) {
							// Use this original start value.
//							task->sw.import("main", pm.head->value);
						}
						break;
					case BLOCK_MANAGER:
						if(!strcmp(pm.head->data, "index")) {
							strcpy(index, pm.get("index"));	// remember the index
							task->bm.init(task->sm.get("todo_count"), task->sm.get("block_size"), atoi(task->sm.get("timeout")));
						}
						break;
					case BLOCK:
						if(!strcmp(pm.head->data, "index")) {
							BN_dec2bn(&(block->index), pm.head->value);
						}
						else if(!strcmp(pm.head->data, "count")) {
							BN_dec2bn(&(block->count), pm.head->value);
						}
						else if(!strcmp(pm.head->data, "complete")) {
							block->complete = atoi(pm.head->value);
						}					
						// restore the block_manager index
						BN_dec2bn(&(task->bm.index), index);
						break;
				}
			}
		}

		fclose(file);
	}
}

void state_write(FILE *pFile, char *data, char *value) {
	char *tmp_str = new char[strlen(data) + strlen(value) + 3];	// include 3 for '=', '\n' & '\0'
	sprintf(tmp_str, "%s=%s\n", data, value);
	fwrite(tmp_str, 1, strlen(tmp_str), pFile);

	delete tmp_str;
}

void state_write(FILE *pFile, char *data, BIGNUM *value) {
	char *tmp_bn = BN_bn2dec(value);

	char *tmp_str = new char[strlen(data) + strlen(tmp_bn) + 3];	// include 3 for '=', '\n' & '\0'
	sprintf(tmp_str, "%s=%s\n", data, tmp_bn);
	fwrite(tmp_str, 1, strlen(tmp_str), pFile);

	delete tmp_str;
	OPENSSL_free(tmp_bn);
}

// Deconstructor
TaskManager::~TaskManager() {
	printf("Saving State ...\n");

	Task *slider = this->head;
	Task *tmp_t;
	char data[DEFAULT_BUFLEN];

	FILE *pFile = fopen("state.ini", "w");
	
	sprintf(data, "[task_manager]\n");
	fwrite(data, 1, strlen(data), pFile);

	state_write(pFile, "id", this->id);

	// loop through each task freeing up the memory
	while(slider != NULL) {
		sprintf(data, "[task]\n");
		fwrite(data, 1, strlen(data), pFile);

		state_write(pFile, "id", slider->id);

		sprintf(data, "%d", slider->state);
		state_write(pFile, "status", data);

		sprintf(data, "[detail]\n");
		fwrite(data, 1, strlen(data), pFile);

		Param *next = slider->sm.head;
		while(next != NULL) {
			state_write(pFile, next->data, next->value);

			next = next->next;
		}

		// block manager:
		sprintf(data, "[block_manager]\n");
		fwrite(data, 1, strlen(data), pFile);

		state_write(pFile, "todo_count", slider->bm.todo_count);
		state_write(pFile, "block_size", slider->bm.block_size);
		state_write(pFile, "index", slider->bm.index);
		
		sprintf(data, "%d", slider->bm.timeout);
		state_write(pFile, "timeout", data);

		// blocks:
		Block *block = slider->bm.head;
		while(block != NULL) {
			sprintf(data, "[block]\n");
			fwrite(data, 1, strlen(data), pFile);

			state_write(pFile, "index", block->index);
			state_write(pFile, "count", block->count);

			sprintf(data, "%d", block->complete);
			state_write(pFile, "complete", data);

			block = block->next;
		}

		// clean up memory usage
		tmp_t = slider;
		slider = slider->next;
		delete tmp_t;
	}

	// close state file
	fclose(pFile);

	// free up the BIGNUM unique_id
	BN_free(this->id);
}

// add a new task
Task* TaskManager::add(void) {
	Task *slider = this->head;

//	printf("[TaskManager::add]\r\n");

	// this will be the first task
	if(slider == NULL) {
		this->head = new Task;
		this->head->prev = NULL;
		this->head->next = NULL;

		slider = this->head;
	}
	// not the first task, add to end of list
	else {
		// advanced till slider is the last node
		while(slider->next != NULL) {
			slider = slider->next;
		}
		// at end of list, attach new task
		slider->next = new Task;
		slider->next->prev = slider;
		slider->next->next = NULL;

		slider = slider->next;
	}

	// set initial values
	slider->id = BN_bn2dec(this->id);
	slider->state = PENDING;
	slider->sw.set_mode(REAL_TIME);

	// increment unique_id
	BIGNUM *num = BN_new();
	if(num != NULL) {
		BN_set_word(num, 1);
		BN_add(this->id, this->id, num);
	}
	BN_free(num);

	// return pointer to new task
	return slider;
}

// del a task by the specified id
bool TaskManager::del(char *id) {
	Task *slider = this->head;

//	printf("[TaskManager::del]\r\n");

	if(slider == NULL) {
		return false;
	}

	while(slider != NULL) {
		if(!strcmp(slider->id, id)) {
			break;
		}
		slider = slider->next;
	}

	if(slider == NULL) {
		return false;
	}

	if(slider == this->head) {
		this->head = slider->next;
		if(this->head != NULL) {
			this->head->prev = NULL;
		}
	}
	else {
		if(slider->prev != NULL) {
			slider->prev->next = slider->next;
		}
		if(slider->next != NULL) {
			slider->next->prev = slider->prev;
		}
	}

	OPENSSL_free(slider->id);
	delete slider;

	return true;
}

// get a task by the specified id
Task* TaskManager::get(char *id) {
	Task *slider = this->head;

	// loop through each task
	while(slider != NULL) {
		// check if this is the task we are looking for
		if(!strcmp(slider->id, id)) {
			return slider;
		}

		// advanced till slider to next node
		slider = slider->next;
	}

	return NULL;
}

// lists current tasks
void TaskManager::list(Connection *conn, char *state) {
	char data[DEFAULT_BUFLEN];
	Task *slider;
	bool match;
	int count = 0;

	// count number of tasks to list
	slider = head;
	while(slider != NULL) {
		match = false;

		if(!strcmp(state, "active")) {
			if(slider->state == ACTIVE) {
				match = true;
			}
		}
		else if(!strcmp(state, "complete")) {
			if(slider->state == COMPLETE) {
				match = true;
			}
		}
		// status == ANY, or undefined same as ANY
		else {
			match = true;
		}

		if(match) {
			count ++;
		}

		slider = slider->next;
	}

	sprintf(data, "cmd=task_list_reply|status=0|count=%d\r\n", count);
	conn->write(data);

	// loop again sending back each task to list
	slider = head;
	while(slider != NULL) {
		match = false;

		if(!strcmp(state, "active")) {
			if(slider->state == ACTIVE) {
				match = true;
			}
		}
		else if(!strcmp(state, "complete")) {
			if(slider->state == COMPLETE) {
				match = true;
			}
		}
		// status == ANY, or undefined same as ANY
		else {
			match = true;
		}

		if(match) {
			switch(slider->state) {
			case PENDING:
				sprintf(data, "cmd=task_list_item|id=%s|timestamp=%d|priority=%s|state=%d|todo=%s|complete=%s|total=%s|seconds=%.2f\r\n",
							slider->id, 0, slider->sm.get("priority"), slider->state, slider->sm.get("todo"),
							slider->bm.getStatus(), slider->sm.get("todo_count"), 0);
				break;
			case ACTIVE:
				sprintf(data, "cmd=task_list_item|id=%s|timestamp=%s|priority=%s|state=%d|todo=%s|complete=%s|total=%s|seconds=%.2f\r\n",
							slider->id, slider->sm.get("timestamp"), slider->sm.get("priority"), slider->state, slider->sm.get("todo"),
							slider->bm.getStatus(), slider->sm.get("todo_count"), slider->sw.get_time_so_far("main"));
				break;
			case COMPLETE:
				sprintf(data, "cmd=task_list_item|id=%s|timestamp=%s|priority=%s|state=%d|todo=%s|complete=%s|total=%s|seconds=%.2f\r\n",
							slider->id, slider->sm.get("timestamp"), slider->sm.get("priority"), slider->state, slider->sm.get("todo"),
							slider->bm.getStatus(), slider->sm.get("todo_count"), slider->sw.get_total_time("main"));
				break;
			}
			conn->write(data);
		}

		slider = slider->next;
	}
}